#include <iostream>
#include <ctime>
#include <Server.h>
#include <Client.h>
#include <Threading.h>
#include <openssl/rc4.h>
using namespace std;

struct DataForDistributionThread
{
	ClientNode* client_data_ptr;
	WindowsThreading* obj_ptr;
};
struct DataForResourceThread
{
	DataForDistributionThread* data_for_distribution_thread; 
	int total_resources;
	int thread_number;
};

Server::Server(int pType, int pServerPort): Type(pType), server_port(pServerPort)
{
	head_client_ptr = NULL;
	tail_client_ptr = NULL;
	#ifdef WIN32
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
	{ 
		cerr << "WSAStartup failed." << endl;
		exit(1);
	}
	#endif
}

int Server::CreateSocket(int pType)			// 0 = TCP, 1 = UDP; default is to create TCP socket.
{
	if (pType == TCPSOCKET)
	{
		Type = TCPSOCKET;
		error_check = server_socket_FD = (int)socket(AF_INET, SOCK_STREAM, 0);
	}
	else
	{
		Type = UDPSOCKET;
		error_check = server_socket_FD = (int)socket(AF_INET, SOCK_DGRAM, 0);
	}
	if (error_check == -1)
	{
		cerr << "ERROR001: Creating socket of type " << pType << endl;
	}
	return error_check;
}

int Server::SetSocketOptions()					// Set socket options to reuse address.
{
	int Yes = 1;
	error_check = setsockopt(server_socket_FD, SOL_SOCKET, SO_REUSEADDR, (char*)&Yes, sizeof(int));
	if (error_check == -1)
	{
		cerr << "ERROR002: Setting socket options. " << endl;
	}
	return error_check;
}

int Server::InitialiseAddress(int pPort)	// Default Server port is 6000.
{
	server_port = pPort;
	// Server address initialisation for binding.
	server_address.sin_family = AF_INET;				// Socekt family.
	server_address.sin_addr.s_addr = htonl(INADDR_ANY);		// Setting server IP. INADDR_ANY blank IP.
	server_address.sin_port = htons(server_port);	// Setting server port.
	fill((char*)&(server_address.sin_zero), (char*)&(server_address.sin_zero)+8, '\0');
	return 0;
}

int Server::Bind()								// Bind Server socket with address.
{
	error_check = bind(server_socket_FD, (sockaddr *)&server_address, sizeof(server_address));
	if (error_check == -1)
	{
		cerr << "ERROR003: Binding." << endl;
	}
	return error_check;
}

int Server::Listen()								// Listen for incoming connections; for TCP Server.
{
	error_check = listen(server_socket_FD, 0);
	if (error_check == -1)
	{
		cerr << "ERROR004: Listening." << endl;
	}
	return error_check;
}

int Server::Accept(ClientNode* client_ptr)								// Accept incoming connections.
{
	socklen_t client_address_length = sizeof(client_ptr->client_address);
	error_check = client_ptr->client_socket_FD = (int)accept(server_socket_FD, (sockaddr*)&client_ptr->client_address, &client_address_length);
	if (error_check == -1)
	{
		cerr << "ERROR005: Accepting." << endl;
		return error_check;
	}
	cout << "*** Server got connection from " << inet_ntoa(client_ptr->client_address.sin_addr) << " on socket '" << client_ptr->client_socket_FD << "' ***" << endl;
	return error_check;
}

int Server::Receive(ClientNode* client_ptr)
{
	error_check = client_ptr->num_of_bytes_received = recv(client_ptr->client_socket_FD, client_ptr->buffer, MAXBUFFERSIZE-1, 0);
	if (error_check == -1)
	{
		cerr << "ERROR006 Receiving" << endl;
		return error_check;
	}
	client_ptr->buffer[client_ptr->num_of_bytes_received] = '\0';
	return error_check;
}

int Server::Receive(void* Data, int DataSize, ClientNode* client_ptr)
{
	error_check = client_ptr->num_of_bytes_received = recv(client_ptr->client_socket_FD, (char*)Data, DataSize, 0);
	if (error_check == -1)
	{
		cerr << "ERROR006 Receiving" << endl;
		return error_check;
	}
	return error_check;
}

int Server::Send(void* Data, int DataSize, ClientNode* client_ptr)
{
	error_check = client_ptr->num_of_bytes_sent = send(client_ptr->client_socket_FD, (char*)Data, DataSize, 0);
	if (error_check == -1)
	{
		cerr << "ERROR007: Server Sending. " << endl;
		return error_check;
	}
	return error_check;
}

int Server::CloseServerSocket()
{
	error_check = close(server_socket_FD);
	return error_check;
}

int Server::CloseClientSocket(ClientNode* client_ptr)
{
	error_check = close(client_ptr->client_socket_FD);
	return error_check;
}

// Additional Functions.
int Server::DisplayServerInfo()
{
	cout << "Server Address: " << inet_ntoa(server_address.sin_addr) << endl;
	cout << "Server Port   : " << ntohs(server_address.sin_port) << endl;
	cout << "Server Socket : " << server_socket_FD << endl;
	return 0;
}

int Server::DisplayClientInfo(ClientNode* client_ptr)
{
	cout << "Client Address: " << inet_ntoa(client_ptr->client_address.sin_addr) << endl;
	cout << "Client Port   : " << ntohs(client_ptr->client_address.sin_port) << endl;
	cout << "Client Socket : " << client_ptr->client_socket_FD << endl;
	return 0;
}

int Server::GetNumOfBytesSent(ClientNode* client_ptr)
{
	return client_ptr->num_of_bytes_sent;
}

int Server::GetNumOfBytesReceived(ClientNode* client_ptr)
{
	return client_ptr->num_of_bytes_received;
}

char* Server::GetBuffer(ClientNode* client_ptr)
{
	return client_ptr->buffer;
}

int Server::AddNewClient(Server* resource_ptr)
{
	ClientNode* new_client_ptr		= new ClientNode;
	ClientNode* current_client_ptr	= new ClientNode;
	
	new_client_ptr->next_client_ptr= NULL;
	Accept(new_client_ptr); 
	DisplayClientInfo(new_client_ptr); 
	
	/*insertion at tail*/
	if(head_client_ptr == NULL)
	{
		new_client_ptr->previous_client_ptr = NULL;
		head_client_ptr = new_client_ptr; 
	}
	else
	{
		current_client_ptr = head_client_ptr;
		while(current_client_ptr->next_client_ptr != NULL)
			current_client_ptr = current_client_ptr->next_client_ptr;
		current_client_ptr->next_client_ptr = new_client_ptr;
	}

	new_client_ptr->server_ptr_clients = this;
	new_client_ptr->server_ptr_resource = resource_ptr;
	new_client_ptr->client_thread_ID = (HANDLE)_beginthread(ThreadFunctionForClients,0,(void*)new_client_ptr);

	return 1;
}

int Server::AddNewResource()
{
	ClientNode* new_client_ptr		= new ClientNode;
	ClientNode* current_client_ptr	= new ClientNode;
	
	new_client_ptr->next_client_ptr= NULL;
	Accept(new_client_ptr); 
	DisplayClientInfo(new_client_ptr); 
	
	/*insertion at tail*/
	if(head_client_ptr == NULL)
	{
		new_client_ptr->previous_client_ptr = NULL;
		head_client_ptr = new_client_ptr; 
	}
	else
	{
		current_client_ptr = head_client_ptr;
		while(current_client_ptr->next_client_ptr != NULL)
			current_client_ptr = current_client_ptr->next_client_ptr;
		current_client_ptr->next_client_ptr = new_client_ptr;
	}
	return 1;
}
/**Encryption Function**/
char* Encrypt(unsigned char *keydata, char *plaintext, int Length)
{
	char *encrypted = new char[Length];
	RC4_KEY key;
	RC4_set_key(&key, strlen((const char*)keydata), keydata);

	// Encryption
	RC4(&key, Length, (const unsigned char*)plaintext, (unsigned char*)encrypted);
//	encrypted[Length] = '\0';

	return encrypted;
}

void ThreadFunctionForResourceListening(void*ptr)
{
	Server* server_obj;
	server_obj = (Server*)ptr;
	while(true)
	{
		// Listen for incoming connections.
		server_obj->Listen();
		server_obj->AddNewResource();
	}
	server_obj->CloseServerSocket();

}
void ThreadFunctionForClientListening(void*ptr)
{
	DataForClientListeningThread* data_ptr;
	data_ptr = (DataForClientListeningThread*)ptr;
	while(true)
	{
		// Listen for incoming connections.
		data_ptr->server_ptr_clients->Listen();
		data_ptr->server_ptr_clients->AddNewClient(data_ptr->server_ptr_resource);
	}
	data_ptr->server_ptr_clients->CloseServerSocket();
}

/** Global thread function for handling multiple clients **/
void ThreadFunctionForClients(void* ptr)
{
	ClientNode* client_data_ptr;
	client_data_ptr = (ClientNode*)ptr;

	WindowsThreading Obj;

	/** recieving **/
	int *recived_buffer,*recived_bufferi,*recived_bufferj,*recived_size;	
	client_data_ptr->server_ptr_clients->Receive(client_data_ptr);
//	cout << "No. of Bytes received: " << client_data_ptr->server_ptr->GetNumOfBytesReceived(client_data_ptr->thread_number) << endl;
	char* buff1	 =	client_data_ptr->server_ptr_clients->GetBuffer(client_data_ptr);
	recived_size =  (int*)buff1;
	char reply[]	= "Size received\n";
	int total_bytes	= recived_size[0];
	client_data_ptr->server_ptr_clients->Send((void*)reply,sizeof(reply),client_data_ptr);
	recived_bufferj	= new int[total_bytes/4];
	recived_bufferi	= new int[total_bytes/4];
	int recived_bytes = 0;
	while(true)
	{
		if(client_data_ptr->server_ptr_clients->GetNumOfBytesReceived(client_data_ptr)==-1 || recived_bytes>=total_bytes)
			break;
		client_data_ptr->server_ptr_clients->Receive((void*)(recived_bufferi),(int)total_bytes,client_data_ptr);
//		cout<<client_data_ptr->server_ptr_clients->GetNumOfBytesReceived(client_data_ptr->thread_number)<<endl;
		Obj.MemoryCopy((char*)(recived_bufferj)+recived_bytes,(char*)recived_bufferi,client_data_ptr->server_ptr_clients->GetNumOfBytesReceived(client_data_ptr));
		recived_bytes += client_data_ptr->server_ptr_clients->GetNumOfBytesReceived(client_data_ptr);
	}
	cout << "No. of Bytes received: " <<recived_bytes<< endl;
	unsigned char keydata[] = "nucesfast";
	char *Decrypted = Encrypt(keydata, (char*)recived_bufferj, total_bytes);
	recived_buffer = (int*)Decrypted;
	
	/** setting the values **/

	Obj.SetRowsMatrixA(recived_buffer[0]);
	Obj.SetColumsMatrixA(recived_buffer[1]);
	Obj.SetRowsMatrixB(recived_buffer[2]);
	Obj.SetColumsMatrixB(recived_buffer[3]);	
	Obj.SetMatrixA( recived_buffer+4);
	Obj.SetMatrixB(recived_buffer + (Obj.GetRowsMatrixA())*(Obj.GetColumsMatrixA()) + 4);
	
	/** Dynamically Assigning memory **/
	Obj.AssignMemory(MATRIXC);

//	time_t start_time[MAXNUMBEROFCLIENTS], end_time[MAXNUMBEROFCLIENTS], time_difference[MAXNUMBEROFCLIENTS];

	/** creating dynamic thread array **/
	HANDLE resource_func_thread_ID;

//	start_time[client_data_ptr] = clock();
	
	DataForDistributionThread data_for_distribution;
	data_for_distribution.obj_ptr = &Obj;
	data_for_distribution.client_data_ptr = client_data_ptr;
	cout<<"starting distribution thread"<<endl;
	resource_func_thread_ID = (HANDLE)_beginthread(ThreadFunctionForDistribution,0,(void*)&data_for_distribution);
	cout<<"wating distribution thread"<<endl;
	WaitForSingleObject(resource_func_thread_ID, INFINITE);
	cout<<"end distribution thread"<<endl;
//	end_time[client_data_ptr] = clock();
//	time_difference[client_data_ptr] = end_time[client_data_ptr] - start_time[client_data_ptr];
	
//	cout << "Time taken =  "<< time_difference[client_data_ptr] <<"(msec)"<< endl;

	/** Sending**/
	cout<<"Start Sending result"<<endl;

	int	matrixC_length=Obj.GetColumsMatrixB()* Obj.GetRowsMatrixA();
	unsigned char *matrix_C_Buffer= new unsigned char[sizeof(int)*matrixC_length];
	memcpy(matrix_C_Buffer, Obj.GetMatrixC(), sizeof(int)*matrixC_length);
	int totalbytes1=matrixC_length*4;
	int sentbytes=0;
	char *Encrypted = Encrypt(keydata, (char*)matrix_C_Buffer, totalbytes1);
	while(true)
	{
		if(client_data_ptr->server_ptr_clients->GetNumOfBytesSent(client_data_ptr)==-1 || sentbytes>=totalbytes1)
			break;
		client_data_ptr->server_ptr_clients->Send((void*)(Encrypted+sentbytes),totalbytes1,client_data_ptr);//sending matrix 
		sentbytes+=client_data_ptr->server_ptr_clients->GetNumOfBytesSent(client_data_ptr);
	}
	cout<<"Sending done"<<endl;

	/** Storing in files **/
//	Obj.MatrixStore(MATRIXA);
//	Obj.MatrixStore(MATRIXB);
	Obj.MatrixStore(MATRIXC);
//////-/------------------////////
	//Obj.MatrixStore(MATRIXC);
//	delete recived_buffer;
	delete recived_bufferi;
	delete recived_bufferj;
	delete matrix_C_Buffer;
//	delete Encrypted;

	client_data_ptr->server_ptr_clients->CloseClientSocket(client_data_ptr); // Close sockets.
	//client_data_ptr->server_ptr_clients->DeleteCurrentClient(client_data_ptr);
	//	delete client_data_ptr;
}

void ThreadFunctionForDistribution(void* ptr)
{
	DataForDistributionThread* data_ptr;
	data_ptr = (DataForDistributionThread*)ptr;
	ClientNode* current_ptr;
	int matrix_A_rows,matrix_B_colums;
	//int matrixA_length,matrixB_length;
	int start_matrix_A, end_matrix_A;	
	int *temp;
	//temp = new int* [number_of_resources];
	
	matrix_A_rows	= data_ptr->obj_ptr->GetRowsMatrixA();
	matrix_B_colums = data_ptr->obj_ptr->GetColumsMatrixB();
	temp = new int [matrix_A_rows*matrix_B_colums*sizeof(int)];

	current_ptr = data_ptr->client_data_ptr->server_ptr_resource->head_client_ptr;
	int counter = 1;
	while(current_ptr->next_client_ptr != NULL)
	{
		current_ptr = current_ptr->next_client_ptr;
		counter++;
	}
	DataForResourceThread* data_ptr_for_resource;	
	data_ptr_for_resource = new DataForResourceThread[counter];
	data_ptr->obj_ptr->AssignMemoryToArray(NUMBEROFRESOURCES);
	for(int i=0; i<counter; i++)
	{
		data_ptr_for_resource[i].data_for_distribution_thread = data_ptr;
		data_ptr_for_resource[i].total_resources = counter;
		data_ptr_for_resource[i].thread_number = -1;
	}
	cout<<data_ptr->obj_ptr->GetRowsMatrixA()<<endl;
	HANDLE* thread_array;
	thread_array = new HANDLE[counter];
	
	cout<<"starting threadfor resource"<<endl;
	for(int i=0; i<counter; i++)
	{
		data_ptr_for_resource[i].thread_number = i;
		thread_array[i] = (HANDLE)_beginthread(ThreadFunctionForResources,0,(void*)&data_ptr_for_resource[i]);
	}
	cout<<"waiting threadfor resource"<<endl;
	for(int i=0; i<counter; i++)
		WaitForSingleObject(thread_array[i], INFINITE);
	cout<<"end thread distribution"<<endl;
	
	for(int count = 0;count<counter; count++)
	{
		start_matrix_A = count*(matrix_A_rows)/NUMBEROFRESOURCES;
		end_matrix_A = ((count+1)*(matrix_A_rows))/NUMBEROFRESOURCES;
		for(int i=0; i<((end_matrix_A - start_matrix_A)*matrix_B_colums+1); i++)
			temp[start_matrix_A*matrix_B_colums+i] = (data_ptr->obj_ptr->matrix_C_array[count][i]);
	}
	data_ptr->obj_ptr->SetMatrixC(temp);
	//delete temp;
	//delete data_ptr->obj_ptr->matrix_C_array ;
}

void ThreadFunctionForResources(void* ptr)
{
	cout<<"starting thread for resource"<<endl;
	DataForResourceThread* data_ptr;
	data_ptr = (DataForResourceThread*)ptr;
	ClientNode* current_ptr;
	current_ptr = data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->head_client_ptr;
	for(int i=0; i<data_ptr->thread_number;i++)
		current_ptr = current_ptr->next_client_ptr;
	int matrix_A_rows,matrix_A_colums,matrix_B_rows,matrix_B_colums;
	int matrixA_length,matrixB_length;
	int start_matrix_A, end_matrix_A;	
	matrix_A_rows	= data_ptr->data_for_distribution_thread->obj_ptr->GetRowsMatrixA();
	start_matrix_A  = matrix_A_rows * data_ptr->thread_number / data_ptr->total_resources;
	end_matrix_A    = matrix_A_rows * (data_ptr->thread_number + 1) / data_ptr->total_resources ;
	matrix_A_colums = data_ptr->data_for_distribution_thread->obj_ptr->GetColumsMatrixA();
	matrix_B_rows	= data_ptr->data_for_distribution_thread->obj_ptr->GetRowsMatrixB();
	matrix_B_colums = data_ptr->data_for_distribution_thread->obj_ptr->GetColumsMatrixB();
	matrixA_length	= matrix_A_rows * matrix_A_colums * sizeof(int);
	matrixB_length	= matrix_B_rows * matrix_B_colums * sizeof(int);
	unsigned int total_bytes = HEADERSIZE*sizeof(int)+matrixA_length+matrixB_length;

	/** sending **/
	char *buffer = new char[total_bytes];
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer, &matrix_A_rows, sizeof(int));
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+sizeof(int), &matrix_A_colums, sizeof(int));
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+2*sizeof(int), &matrix_B_rows, sizeof(int));
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+3*sizeof(int), &matrix_B_colums, sizeof(int));
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+4*sizeof(int), &start_matrix_A, sizeof(int));
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+5*sizeof(int), &end_matrix_A, sizeof(int));
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+HEADERSIZE*sizeof(int), data_ptr->data_for_distribution_thread->obj_ptr->GetMatrixA(), matrixB_length);
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer+HEADERSIZE*sizeof(int)+matrixA_length, data_ptr->data_for_distribution_thread->obj_ptr->GetMatrixB(), matrixB_length);
	/**Encryption**/
	unsigned char keydata[] = "nucesfast";
	char *Encrypted = Encrypt(keydata, buffer, total_bytes);
	

	cout<<"Sending"<<endl;

	int sent_bytes = 0;
	unsigned char buffer_size[sizeof(int)];
	data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(buffer_size, &total_bytes, sizeof(int));
	data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->Send((void*)&buffer_size,sizeof(int),current_ptr);
	data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->Receive(current_ptr);
	char* reply	= data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->GetBuffer(current_ptr);
	
	while(true)
	{
		if(data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->GetNumOfBytesSent(current_ptr) == -1 || sent_bytes>=total_bytes)
			break;
		
		data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->Send((void*)(Encrypted),total_bytes,current_ptr);//sending matrix 
		sent_bytes += data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->GetNumOfBytesSent(current_ptr);
	}

	cout<<"Done sending"<<endl<<endl<<"Recieving"<<endl;
		
	/** Recieving **/
	int matrixC_length=data_ptr->data_for_distribution_thread->obj_ptr->GetColumsMatrixB() * (end_matrix_A - start_matrix_A) * sizeof(int);
	int* recived_bufferj	= new int[matrixC_length/4];
	int* recived_bufferi	= new int[matrixC_length/4];
	int recived_bytes =	0;
	while(true)
	{
		if(data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->GetNumOfBytesReceived(current_ptr)==-1 || recived_bytes>=matrixC_length)
			break;

		data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->Receive((void*)(recived_bufferi),(int)matrixC_length,current_ptr);
		data_ptr->data_for_distribution_thread->obj_ptr->MemoryCopy(recived_bufferj + recived_bytes, recived_bufferi, data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->GetNumOfBytesReceived(current_ptr));
		recived_bytes += data_ptr->data_for_distribution_thread->client_data_ptr->server_ptr_resource->GetNumOfBytesReceived(current_ptr);
	}
	char *Decrypted = Encrypt(keydata, (char*)recived_bufferj, matrixC_length);
	int* recived_Matrix_C	= (int*)Decrypted;
	//memcpy(data_ptr->data_for_distribution_thread->obj_ptr->GetMatrixC() + start_matrix_A*matrix_B_colums*sizeof(int), recived_Matrix_C, (end_matrix_A - start_matrix_A)*matrix_B_colums*sizeof(int));
	
	data_ptr->data_for_distribution_thread->obj_ptr->matrix_C_array[data_ptr->thread_number]=recived_Matrix_C;
	cout<<"Done Recieving"<<endl;
	/** Storing to file just for checking**/
//	data_ptr->data_for_distribution_thread->obj_ptr->MatrixStore(MATRIXA);
//	data_ptr-data_for_distribution_thread->>obj_ptr->MatrixStore(MATRIXB);
//	data_ptr->data_for_distribution_thread->obj_ptr->MatrixStore(MATRIXC);

//	cout<<"Done Storing"<<endl<<"Deleting Memory"<<endl;
	/** Deleting allocated memory **/
	delete	recived_bufferi;
	delete	recived_bufferj;
//	delete Encrypted;
//	delete recived_Matrix_C;
	delete buffer;
	
	cout<<"Done Deleting and thread end"<<endl;

}